﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace UIT.Commands
{
    public class CommandProcessor
    {
        private static int capacity = 20;
        private IList<ICommand> stack;
        private int currentIndex;
        private CommandManager manager;

        public CommandManager Manager { get { return manager; } }

        public CommandProcessor()
            : this(new CommandManager())
        {

        }

        public CommandProcessor(CommandManager manager)
        {
            this.manager = manager;
            stack = new List<ICommand>(capacity);
        }

        public bool Undo_Enabled
        {
            get { return stack.Count > 0 && currentIndex < stack.Count; }
        }

        public bool Redo_Enabled
        {
            get { return currentIndex > 0; }
        }

        public void ExecuteCommand(CommandMetadata metadata, object target, params object[] parameters)
        {
            ExecuteCommand((ICommand)Activator.CreateInstance(metadata.CommandType), target, parameters);
        }

        public void ExecuteCommand(string commandName, object target, params object[] parameters)
        {
            ExecuteCommand(manager, commandName, target, parameters);
        }

        public void ExecuteCommand(ICommandBuilder commandBuilder, string commandName, object target, params object[] parameters)
        {
            ExecuteCommand(commandBuilder.GetCommand(commandName), target, parameters);
        }

        /// <summary>
        /// Executes a ICommand
        /// </summary>
        /// <param name="command">the Command to execute</param>
        /// <param name="target">the Command’s target</param>
        /// <param name="parameters">the Command’s parameters</param>
        public void ExecuteCommand(ICommand command, object target, params object[] parameters)
        {
            command.Execute(target, parameters);

            switch (command.CommandType)
            {
                case CommandType.Irreversible: // we clear the history
                    this.currentIndex = 0;
                    this.stack.Clear();
                    break;
                case CommandType.Undoable: // we register the command
                    if (this.stack.Count == capacity)
                    {
                        this.stack.RemoveAt(0);
                    }
                    // When we execute a command, we must check the current position
                    // if position > 0, we remove every command found after this position and add the new command at the end
                    if (this.currentIndex > 0)
                    {
                        this.stack = this.stack.Take(this.currentIndex).ToList();
                        this.currentIndex = 0;
                    }
                    this.stack.Add(command);
                    break;
                default:
                    break;
            }
        }

        /// <summary>
        /// The CommandProcessor must execute the command found at the previous position in the stack
        /// </summary>
        public void Undo()
        {
            if (stack.Count == 0 || stack.Count == currentIndex)
                return;
            ICommand command = stack[stack.Count - currentIndex - 1]; // take the previous command
            this.currentIndex++;
            if (currentIndex > stack.Count)
            {
                currentIndex = stack.Count;
            }
            command.Undo();
        }

        /// <summary>
        /// The CommandProcessor must execute the command found at the next position in the stack
        /// </summary>
        public void Redo()
        {
            if (this.currentIndex == 0)
                return;
            ICommand command = stack[stack.Count - this.currentIndex]; // take the next command
            if (this.currentIndex > 0)
            {
                this.currentIndex--;
            }
            command.Redo();
        }
    }
}
